﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ParticleSystem
{

    public class Emitter : RFBase
    {
        //used in many places that's why its in declaration place
        public Random random;
        public string Name;
        //ID of texture which will be used
        public int Image;          
        //effects velocity Y
        public float Gravity;
		//effects velocty X
        public float Wind;
        //how long the delay between spawned particles
        public float Rate;
        public float Elapsed = 0f;
        public int Amount = 0;
        public int Amount_Max;
        //will help us to give the control over preview
        public bool started = false;
        //center from where particles will be spread
        public int position_X = 386 / 2;
        public int position_Y = 386 / 2;
        //particles list
        [System.Xml.Serialization.XmlIgnore]
        public Particle[] parts;
        //size
        public int Size_X = 0;
        public int Size_Y = 0;
        public int Width = 300;
        public int Height = 300;
        //offset is used for collision, to take into consideration 2px borders
        public int Offset = 0;
        public bool AutoRespawn = false; //when true continues to respawn effect
        public bool useBoundingBox = true;

        //playing speed
        public float playingSpeedRate = 0f;
        public float playingSpeedDelay = 0f;

        //randomizers properties
        public int LifeTime_Random_Start;
        public int LifeTime_Random_End;

        public float Velocity_Random_Start;
        public float Velocity_Random_end;

        public float Alpha_Random_Start_Start;
        public float Alpha_Random_Start_End;

        public float Alpha_Random_End_Start;
        public float Alpha_Random_End_End;

        public float Scale_Random_Start_Start;
        public float Scale_Random_Start_End;

        public float Scale_Random_End_Start;
        public float Scale_Random_End_End;

        public float Rotation_Random_Start;
        public float Rotation_Random_End;

        public int Angle_Random_Start;
        public int Angle_Random_End;

        public void Init(int amount)
        {
            Amount_Max = amount;
            EmitterInit();
            RandomizerInit();
            ParticlesInit();
        }

        public void EmitterInit()
        {
            //used in many places that's why its in declaration place
            random = new Random();
            Name = "UNKNOWN";
            setSizeRectangle();
        }

        public void ParticlesInit()
        {
            //PARTICLES INITIALIZATION
            parts = new Particle[Amount_Max];
            for (int i = 0; i < Amount_Max; i++)
            {
                parts[i] = new Particle();
                parts[i].Position_X = position_X;
                parts[i].Position_Y = position_Y;
                parts[i].Alive = true;
            }

            updateParticlesVelocity(Velocity_Random_Start, Velocity_Random_end);
            updateParticlesLifetime(LifeTime_Random_Start, LifeTime_Random_End);
            updateParticlesAngle(Angle_Random_Start, Angle_Random_End);
            updateParticlesScaleBegin(Scale_Random_Start_Start, Scale_Random_Start_End);
            updateParticlesScaleEnd(Scale_Random_End_Start, Scale_Random_End_End);
            updateParticlesAlphaBegin(Alpha_Random_Start_Start, Alpha_Random_Start_End);
            updateParticlesAlphaEnd(Alpha_Random_End_Start, Alpha_Random_End_End);
            updateParticlesRotationIncr(Rotation_Random_Start, Rotation_Random_End);
            Respawn();
        }

        private void RandomizerInit()
        {
            LifeTime_Random_Start = 1000;
            LifeTime_Random_End = 1000;

            Velocity_Random_Start = 2f;
            Velocity_Random_end = 2f;

            //alpha, scale, rotation will be in % and degrees.
            //Instead of 1f which represents fully visible it will be 100, which represents 100%
            //when in calculation it will be divided by 100.
            Alpha_Random_Start_Start = 100;
            Alpha_Random_Start_End = 100;

            Alpha_Random_End_Start = 50;
            Alpha_Random_End_End = 50;

            Scale_Random_Start_Start = 100;
            Scale_Random_Start_End = 100;

            Scale_Random_End_Start = 50;
            Scale_Random_End_End = 50;

            Rotation_Random_Start = 10;
            Rotation_Random_End = 10;

            Angle_Random_Start = 0;
            Angle_Random_End = 180;
        }

        public void Respawn()
        {
            Amount = 0;
            Elapsed = 0f;

            foreach (Particle part in parts)
                part.Alive = false;

            for (int i = 0; i < Amount_Max; i++)
            {
                parts[i].Position_X = position_X;
                parts[i].Position_Y = position_Y;
                parts[i].LifeTime = parts[i].LifeTime_Start;
                parts[i].Velocity_X = parts[i].Velocity_X_Start;
                parts[i].Velocity_Y = parts[i].Velocity_Y_Start;
                parts[i].Scale = parts[i].Scale_Begin;
                parts[i].Rotation = parts[i].Rotation_Start;
                parts[i].Alive = true;
            }
        }

        public void Start()
        {
            started = true;
        }

        public void Pause()
        {
            started = false;
        }

        public void Update(float gameTime)
        {
            if (started)
            {
                Elapsed += gameTime/1000;
                playingSpeedDelay += gameTime / 1000;
                //Console.WriteLine(Elapsed.ToString());
                while (Elapsed >= Rate && Amount < Amount_Max)
                {
                    Elapsed -= Rate;
                    Amount++;

                }

                if (playingSpeedDelay >= playingSpeedRate)
                {
                    for (int i = 0; i < Amount; i++)
                    {
                        ParticleUpdate(i,gameTime);
                    }
                    playingSpeedDelay = 0f;
                }
            }
        }

        private void ParticleUpdate(int i, float gameTime)
        {
            if (parts[i].Alive)
            {
                if (useBoundingBox)
                {
                    //check for collision before anything else
                    if (checkParticle(parts[i].Position_X, parts[i].Position_Y))
                        parts[i].LifeTime = 0;
                }
                //if dead
                if (parts[i].LifeTime <= 0)
                {
                    parts[i].Alive = false;
                    if (AutoRespawn && allDead())
                    {
                        Respawn();
                    }
                }
                    //if alive
                else
                {
                    updateParticlePosition(i);
                    parts[i].Alpha = reduceAlpha(i);
                    parts[i].Scale = reduceScale(i);
                    parts[i].Rotation += parts[i].Rotation_Incr;
                    parts[i].LifeTime -= (int)gameTime;
                }
            }
        }

        private void updateParticlePosition(int i)
        {
            applyWind(i);
            applyGravity(i);
            parts[i].Position_X += parts[i].Angle_X * parts[i].Velocity_X;
            parts[i].Position_Y += parts[i].Angle_Y * parts[i].Velocity_Y;
        }

        private void applyWind(int i)
        {
                parts[i].Velocity_X -= Wind;
        }

        private void applyGravity(int i)
        {
            parts[i].Velocity_Y -= Gravity;
        }

        //respawns specific particle making it infinite effect
        private void respawnParticle(int i)
        {
            parts[i].Position_X = position_X;
            parts[i].Position_Y = position_Y;
            parts[i].LifeTime = parts[i].LifeTime_Start;
            parts[i].Alive = true;
            Amount--;
        }

        //check if all particles are dead
        private bool allDead()
        {
            bool alldead = true;
            foreach (Particle particle in parts)
                if (particle.Alive == true)
                    alldead = false;
            return alldead;
            
        }

        private float reduceAlpha(int i)
        {
            float currentAlpha = (parts[i].Alpha_Start * parts[i].LifeTime) / parts[i].LifeTime_Start;
            if (currentAlpha > parts[i].Alpha_End)
                return currentAlpha;
            else
                return parts[i].Alpha_End;
        }

        private float reduceScale(int i)
        {
            float currentScale = (parts[i].Scale_Begin * parts[i].LifeTime) / parts[i].LifeTime_Start;
            if (currentScale > parts[i].Scale_End)
                return currentScale;
            else
                return parts[i].Scale_End;
        }

        public void setSizeRectangle()
        {
            Size_X = (position_X - Width / 2) - 2;
            Size_Y = (position_Y - Height / 2) - 2;
        }

        public bool checkParticle(float position_X, float position_Y)
        {
            bool value = false;
            if (position_X <= Size_X)
                return true;
            if (position_X >= (Size_X + Width - 2))
                return true;
            if (position_Y <= Size_Y)
                return true;
            if (position_Y >= (Size_Y + Height - 2))
                return true;
            else
                return value;
        }

        private float[] AngleToVector(float angle)
        {
            double radians = (Math.PI / 180) * angle;
            //vector[0] = X;
            //vector[1] = Y;
            float[] vector = new float[2];
            vector[0] = (float)Math.Cos(radians);
            vector[1] = -(float)Math.Sin(radians);
            return vector;
        }

        public void updateParticlesVelocity(float velocityStart, float velocityEnd)
        {
            try
            {
                for (int i = 0; i < parts.Length; i++)
                {
                    Velocity_Random_Start = velocityStart;
                    Velocity_Random_end = velocityEnd;
                    float tempNewVelocity = random.Next((int)(Velocity_Random_Start * 100), (int)(Velocity_Random_end * 100)+1);
                    parts[i].Velocity_X = tempNewVelocity / 100;
                    parts[i].Velocity_Y = tempNewVelocity / 100;
                    parts[i].Velocity_X_Start = tempNewVelocity / 100;
                    parts[i].Velocity_Y_Start = tempNewVelocity / 100;

                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void updateParticlesLifetime(int lifetimeStart, int lifetimeEnd)
        {
            try
            {
                for (int i = 0; i < parts.Length; i++)
                {
                    LifeTime_Random_Start = lifetimeStart;
                    LifeTime_Random_End = lifetimeEnd;
                    parts[i].LifeTime_Start = random.Next(LifeTime_Random_Start, LifeTime_Random_End + 1);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void updateParticlesAngle(int angleStart, int angleEnd)
        {
            try
            {
                for (int i = 0; i < parts.Length; i++)
                {
                    Angle_Random_Start = angleStart;
                    Angle_Random_End = angleEnd;
                    float Angle = random.Next(Angle_Random_Start, Angle_Random_End + 1);
                    parts[i].Angle_X = AngleToVector(Angle)[0];
                    parts[i].Angle_Y = AngleToVector(Angle)[1];
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        //changes start for scale, alpha, rotation to make us of % instead of floats
        public void updateParticlesScaleBegin(float scaleBeginBegin, float scaleBeginEnd)
        {
            try
            {
                for (int i = 0; i < parts.Length; i++)
                {
                    Scale_Random_Start_Start = scaleBeginBegin;
                    Scale_Random_Start_End = scaleBeginEnd;
                    float tempScaleBegin = random.Next((int)(Scale_Random_Start_Start), (int)(Scale_Random_Start_End) + 1);//removed *100 for random values
                    parts[i].Scale_Begin = tempScaleBegin / 100;
                    parts[i].Scale = tempScaleBegin / 100;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void updateParticlesScaleEnd(float scaleEndBegin, float scaleEndEnd)
        {
            try
            {
                for (int i = 0; i < parts.Length; i++)
                {
                    Scale_Random_End_Start = scaleEndBegin;
                    Scale_Random_End_End = scaleEndEnd;
                    float tempScaleEnd = random.Next((int)(Scale_Random_End_Start), (int)(Scale_Random_End_End) + 1);//removed *100 for random values
                    parts[i].Scale_End = tempScaleEnd / 100;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void updateParticlesAlphaBegin(float alphaStart, float alphaEnd)
        {
            try
            {
                for (int i = 0; i < parts.Length; i++)
                {
                    Alpha_Random_Start_Start = alphaStart;
                    Alpha_Random_Start_End = alphaEnd;
                    float tempAlphaStart = random.Next((int)(Alpha_Random_Start_Start), (int)(Alpha_Random_Start_End) + 1);//removed *100 for random values
                    parts[i].Alpha_Start = tempAlphaStart / 100;
                    parts[i].Alpha = tempAlphaStart / 100;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void updateParticlesAlphaEnd(float alphaStart, float alphaEnd)
        {
            try
            {
                for (int i = 0; i < parts.Length; i++)
                {
                    Alpha_Random_End_Start = alphaStart;
                    Alpha_Random_End_End = alphaEnd;
                    float tempAlphaStart = random.Next((int)(Alpha_Random_End_Start), (int)(Alpha_Random_End_End) + 1);//removed *100 for random values
                    parts[i].Alpha_End = tempAlphaStart / 100;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void updateParticlesRotationIncr(float rotationIncrBegin, float rotationIncrEnd)
        {
            try
            {
                for (int i = 0; i < parts.Length; i++)
                {
                    Rotation_Random_Start = rotationIncrBegin;
                    Rotation_Random_End = rotationIncrEnd;
                    float tempRotation = random.Next((int)(Rotation_Random_Start), (int)(Rotation_Random_End) + 1);//removed *100 for random values
                    parts[i].Rotation_Incr = tempRotation / 100;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void setStartUpPosition(int width, int height)
        {
            //takes the width and height of the window and makes position in the center
            position_X = width / 2;
            position_Y = height / 2;
            Respawn();
        }
    }
}
